package appCache

import (
	"errors"
	"gitee.com/ling-bin/go-utils/pools"
	"sync"
	"sync/atomic"
	"time"
)

var(
	lock sync.Mutex
	_CacheId int64
	cachePoolMap = sync.Map{}
	timeTaskPool = pools.NewTimeTaskPool(20000, 8, 10000,nil)
)

//GetCacheRepository 获取缓存仺储
func GetCacheRepository(entityName string) ICacheRepository {
	if val, ok := cachePoolMap.Load(entityName); ok {
		return val.(ICacheRepository)
	}
	lock.Lock()
	defer lock.Unlock()
	if val, ok := cachePoolMap.Load(entityName); ok {
		return val.(ICacheRepository)
	}
	repository := &CacheRepository{
		entityName: entityName,
		cacheMap:   sync.Map{},
	}
	cachePoolMap.Store(entityName, repository)
	return repository
}

//CacheRepository 缓存仓储
type CacheRepository struct {
	entityName string
	cacheMap   sync.Map
}

//GetOrAddDuration 获取没有就添加
func (c *CacheRepository) GetOrAddDuration(key string,foundCall func(key string) interface{}, duration time.Duration) (entity interface{},err error) {
	result, ok := c.cacheMap.Load(key)
	if !ok {
		result = foundCall(key)
		c.SetDuration(key, result, duration)
		return result, nil
	}
	cacheItem := result.(*cacheItem)
	ctime := time.Now().Unix()
	if ctime > cacheItem.expireTime {
		return nil, errors.New(" key not found!")
	}
	if cacheItem.duration != 0 {
		cacheItem.expireTime = time.Now().Add(cacheItem.duration).Unix()
	}
	cacheItem.Count++
	return cacheItem.value, nil
}

//GetOrAdd 获取没有就添加
func (c *CacheRepository) GetOrAdd(key string,foundCall func(key string) interface{},ttl int, expireFrontCall func(interface{},int64),frontNotice int) (entity interface{},err error) {
	result, ok := c.cacheMap.Load(key)
	if !ok {
		result = foundCall(key)
		c.Set(key, result, ttl, expireFrontCall, frontNotice)
		return result, nil
	}
	cacheItem := result.(*cacheItem)
	ctime := time.Now().Unix()
	if ctime > cacheItem.expireTime {
		return nil, errors.New(" key not found!")
	}
	if cacheItem.duration != 0 {
		cacheItem.expireTime = time.Now().Add(cacheItem.duration).Unix()
	}
	cacheItem.Count++
	return cacheItem.value, nil
}

//Get 获取单个
//key 数据唯一key
func (c *CacheRepository) Get(key string)(entity interface{},err error) {
	result, ok := c.cacheMap.Load(key)
	if !ok {
		return nil, errors.New(" key not found!")
	}
	cacheItem := result.(*cacheItem)
	ctime := time.Now().Unix()
	if ctime > cacheItem.expireTime {
		return nil, errors.New(" key not found!")
	}
	if cacheItem.duration != 0 {
		cacheItem.expireTime = time.Now().Add(cacheItem.duration).Unix()
	}
	cacheItem.Count++
	return cacheItem.value, nil
}

//Remove 删除单个
// key key值
func (c *CacheRepository) Remove(key string) {
	c.cacheMap.Delete(key)
}

//ClearRun清理
func (c *CacheRepository) clearRun(key string,id int64,expTimeKey int64) {
	result, ok := c.cacheMap.Load(key)
	if !ok {
		return
	}
	cacheItem := result.(*cacheItem)
	if cacheItem.Id != id {
		return
	}
	if cacheItem.expireTime == expTimeKey {
		c.cacheMap.Delete(key)
	} else if cacheItem.expireTime > expTimeKey {
		if cacheItem.expireFrontCall != nil && cacheItem.expireTime == expTimeKey + int64(cacheItem.frontNotice) {
			timeKey := time.Now().Unix()
			timeTaskPool.AddTask(timeKey, func(taskId, expTimeKey int64) {
				cacheItem.expireFrontCall(cacheItem.value, cacheItem.Count)
			})
		}
		//添加延期任务
		timeTaskPool.AddIdTask(cacheItem.Id, cacheItem.expireTime, func(taskId, expTimeKey int64) {
			c.clearRun(key, id, expTimeKey)
		})
	}
}

// SetDuration 插入单个
// key 数据唯一key
// entity  结构体
// duration 滑动到期时间,访问延期
func (c *CacheRepository) SetDuration(key string, entity interface{}, duration time.Duration) {
	id := atomic.AddInt64(&_CacheId, 1)
	cacheItem := &cacheItem{
		key:             key,
		value:           entity,
		duration:        duration,
		expireTime:      time.Now().Add(duration).Unix(),
		expireFrontCall: nil,
		frontNotice:     0,
		Count:           0,
		Id:              id,
	}
	c.cacheMap.Store(key, cacheItem)
	//添加延期任务
	timeTaskPool.AddIdTask(cacheItem.Id, cacheItem.expireTime, func(taskId, expTimeKey int64) {
		c.clearRun(key, id, expTimeKey)
	})
}

// Set 插入单个
// key 数据唯一key
// entity  结构体
// ttl     单位秒 ，0为永不到期
// expireFrontCall  到期前回调
// frontNotice  提前回调时间 秒
func (c *CacheRepository) Set(key string, entity interface{}, ttl int, expireFrontCall func(interface{},int64),frontNotice int) {
	if frontNotice <= 5 {
		frontNotice = 5
	}
	if ttl < 15 {
		ttl = 15
	}
	ttlSecond := time.Second * time.Duration(ttl)
	id :=  atomic.AddInt64(&_CacheId, 1)
	cacheItem := &cacheItem{
		key:             key,
		value:           entity,
		duration:        0,
		expireTime:      time.Now().Add(ttlSecond).Unix(),
		expireFrontCall: expireFrontCall,
		frontNotice:     frontNotice,
		Count:           0,
		Id:              id,
	}
	c.cacheMap.Store(key, cacheItem)
	var timeKey int64
	if expireFrontCall != nil {
		timeKey = cacheItem.expireTime - int64(frontNotice)
	}else {
		timeKey = cacheItem.expireTime
	}
	//添加延期任务
	timeTaskPool.AddIdTask(cacheItem.Id,timeKey, func(taskId, expTimeKey int64) {
		c.clearRun(key,id,expTimeKey)
	})
}

// cache 缓存
type cacheItem struct {
	key             string
	value           interface{}
	duration        time.Duration
	expireTime      int64
	expireFrontCall func(interface{}, int64)
	frontNotice     int
	Count           int64
	Id              int64
}